package coreZapLog

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"reflect"

	"gitee.com/xgh2012/api-core/coreBiz"
	"gitee.com/xgh2012/api-core/coreMiddleware"
	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

var (
	Logger *zap.Logger

	//LoggerSkip 需要跳过封装的文件
	LoggerSkip     *zap.Logger
	LoggerSkipGorm *zap.Logger
)

func InitLogger(cmd string) {
	// 日志文件配置
	logPath := viper.GetString("log_file.path") + cmd + "/"
	if _, err := os.Stat(logPath); err != nil {
		if errors.Is(err, os.ErrNotExist) {
			err = os.MkdirAll(logPath, os.ModePerm)
			if err != nil {
				log.Printf("创建日志目录错误 %+v \n", err.Error())
				panic(fmt.Errorf("创建日志目录错误 %+v \n", err.Error()))
			}
		} else {
			log.Printf("初始化日志目录错误 %+v \n", err.Error())
			panic(fmt.Errorf("初始化日志目录错误 %+v \n", err.Error()))
		}
	}

	hook := &lumberjack.Logger{
		Filename:   logPath + "run.log",                  //文件名 ,指定日志文件的写入路径。保留的备份日志文件将存放在同一目录下。如果为空，则默认使用操作系统临时目录下的 <进程名>-lumberjack.log 文件。
		MaxSize:    viper.GetInt("log_file.max_size"),    //最大文件大小 日志文件在进行轮转（rotate）前的最大大小（单位：MB）。默认为 100 MB。
		MaxBackups: viper.GetInt("log_file.max_backups"), //最大保留天数 根据文件名中编码的时间戳保留旧日志文件的最大天数（注：一天按 24 小时计算，可能因夏令时、闰秒等与日历天数不完全一致）。默认不根据时间删除旧日志文件。
		MaxAge:     viper.GetInt("log_file.max_age"),     //最大备份数 保留旧日志文件的最大数量。默认保留所有旧日志文件（但 MaxAge 仍可能导致其被删除）
		Compress:   viper.GetBool("log_file.compress"),   //压缩备份 确定轮转后的日志文件是否使用 gzip 压缩。默认不压缩。
		LocalTime:  true,                                 //使用本地时间 确定备份文件名中的时间戳是否使用计算机的本地时间。默认为使用 UTC 时间。
	}

	// 日志级别
	level := zapcore.DebugLevel
	logLevel := viper.GetString("log_file.log_level")
	switch logLevel {
	case "debug":
		level = zapcore.DebugLevel
	case "info":
		level = zapcore.InfoLevel
	case "warn":
		level = zapcore.WarnLevel
	case "error":
		level = zapcore.ErrorLevel
	case "panic":
		level = zapcore.PanicLevel
	}

	// 统一 JSON encoder 配置
	jsonEncoderConfig := zapcore.EncoderConfig{
		TimeKey:        "time",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "file",
		MessageKey:     "message",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	}

	fileCore := zapcore.NewCore(
		zapcore.NewJSONEncoder(jsonEncoderConfig),
		zapcore.AddSync(hook),
		level,
	)

	// 开发和生产都双输出（文件+控制台），且都是 JSON
	consoleCore := zapcore.NewCore(
		zapcore.NewJSONEncoder(jsonEncoderConfig),
		zapcore.AddSync(os.Stdout),
		level,
	)
	core := zapcore.NewTee(fileCore, consoleCore)

	//core := zapcore.NewTee(fileCore)

	Logger = zap.New(core, zap.AddCaller())
	LoggerSkipGorm = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(3))
	LoggerSkip = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
}

func WithContext(c context.Context) *zap.Logger {
	if c == nil {
		return LoggerSkip
	}
	var ctx context.Context
	// 判断是否是 *gin.Context
	if reflect.TypeOf(c).String() == "*gin.Context" {
		ctx = coreMiddleware.CreateNewRequestContext(c.(*gin.Context))
	} else {
		ctx = c
	}

	var fields []zap.Field
	if traceID := coreMiddleware.GetTraceId(ctx); traceID != "" {
		fields = append(fields, zap.String(coreMiddleware.CtxKeyTraceId, traceID))
	}

	userInfo := coreMiddleware.GetUserInfo(ctx)
	if !coreBiz.IsEmpty(userInfo.Uid) {
		fields = append(fields, zap.String(coreMiddleware.CtxKeyUid, userInfo.Uid))
	}
	if !coreBiz.IsEmpty(userInfo.Mobile) {
		fields = append(fields, zap.String(coreMiddleware.CtxKeyMobile, userInfo.Mobile))
	}
	if !coreBiz.IsEmpty(userInfo.IdCard) {
		fields = append(fields, zap.String(coreMiddleware.CtxKeyIdCard, userInfo.IdCard))
	}

	if len(fields) > 0 {
		return LoggerSkip.With(fields...)
	}

	return LoggerSkip
}
func InfoCtx(c context.Context, msg string, fields ...zap.Field) {
	WithContext(c).Info(msg, fields...)
}
func ErrorCtx(c context.Context, msg string, fields ...zap.Field) {
	WithContext(c).Error(msg, fields...)
}
func DebugCtx(c context.Context, msg string, fields ...zap.Field) {
	WithContext(c).Debug(msg, fields...)
}
func WarnCtx(c context.Context, msg string, fields ...zap.Field) {
	WithContext(c).Warn(msg, fields...)
}
